Skip to content

pldm: Move the IPC server onto the shared service seam - #499

Merged
chrysh merged 3 commits into
OpenPRoT:ocp-global-demo-wipfrom
9elements:pldm-on-util-service
Sep 26, 2026
Merged

chrysh merged 3 commits into
OpenPRoT:ocp-global-demo-wipfrom
9elements:pldm-on-util-service

Conversation

@chrysh

@chrysh chrysh commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

The first migration onto util_service. Two local copies go away.

ipc-api's Transport trait was character-identical to
util_service::AsyncTransport, down to the doc wording, so the file is
deleted and the api crate is wire format only. ipc-server's
LoopbackTransport was util_service::Loopback wrapped around
dispatch, so it goes too, and the tests that used it now drive
Loopback<FdServer<StubFd>, MAX_RESPONSE_SIZE> instead.

FdServer is the Dispatch impl. The newtype exists because Dispatch
is a foreign trait and cannot be implemented for every F: FdHandler
directly. dispatch() returns Result now: a response buffer too small
for even an error frame is the one case with nothing to send back, where
it used to return a bare 0.

One behavior change worth reviewing on its own. The old loopback rejected
a request longer than MAX_REQUEST_SIZE, and it turned out to be the
only thing doing so. The check is now per opcode: expected_request_len(op)
returns the exact frame size for each operation, with no catch-all, so
adding an opcode without choosing its length fails to compile. A frame
with trailing slack is rejected before dispatch.

26 tests in ipc-server, all passing: the existing dispatch tests plus
the ported loopback round-trips.

Not in here: i2c's migration, and the typed orchestrator-side client.

Review the last commit only; below it is #498.

Progress toward 9elements#13.

@chrysh
chrysh force-pushed the pldm-on-util-service branch from 54e1447 to a8608b8 Compare September 25, 2026 13:34
@chrysh
chrysh force-pushed the pldm-on-util-service branch 3 times, most recently from 6c1d3ef to 6b407e6 Compare September 26, 2026 17:33
Defines the binary protocol the orchestrator uses to talk to the PLDM
Firmware Device over IPC. Twelve operations covering the full update
lifecycle: offer accept/reject, verify/apply grant/deny, activation,
SVN commit, cancel ack, and status query.

Wire format: fixed 8-byte headers for both request and response, with
manual byte encoding (no zerocopy dep) for no_std compatibility. The
doc's response diagram adds to 6 bytes; the prose says "Fixed 8-byte
header," so we pad 2 reserved bytes to match the stated intent. The
doc field `gen` is renamed to `generation` because `gen` is a reserved
keyword in Rust 2024 edition.

All four Deny* ops carry a DenyReason byte for uniformity (the doc
pins DenySvnCommit to PolicyViolation, but sending the reason on the
wire keeps the decode path identical to the other denials).

The FdStatus enum encodes the FD's current condition as a QueryStatus
response payload, covering all DSP0267 states plus pending decisions.
OfferPending carries target, total, transfer mode, and the SVN delayed
flag from the UA's UpdateComponent request, so the orchestrator can
validate against the floor.

The Transport trait is the seam the client crate will be generic over,
split into start/poll/cancel rather than one blocking round-trip: the
orchestrator client runs in the event loop, which must not block. In
production it is backed by util/ipc's AsyncTransaction; host tests use
LoopbackTransport.

Host-buildable with no kernel dependencies. All encode/decode paths
have roundtrip tests.

Assisted-by: Claude Code
The FD side of the IPC channel. dispatch() decodes the orchestrator's
request header, matches on PldmOp, calls the corresponding FdHandler
trait method, and encodes the response. FdHandler has one method per
opcode so the implementation never touches wire bytes.

LoopbackTransport implements pldm_ipc_api::Transport by calling
dispatch directly in-process. Dispatch runs inside start(), so the
response is ready on the first poll(); the split-phase shape is kept
so client code written against a real channel runs unchanged here.
The loopback tests show a multi-step sequence (accept offer, then
query status shows ReadyXfer), proving the wire format, dispatch, and
status encoding work together, plus the state rules: start while
pending, poll with nothing in flight, cancel, and a short response
buffer.

Same pattern as services/i2c/server: dispatch generic over a trait,
loopback transport in the server crate, host-buildable with no kernel
dependencies.

Assisted-by: Claude Code
Deletes the two local copies: ipc-api's Transport trait, which was
character-identical to util_service::AsyncTransport, and ipc-server's
LoopbackTransport, which was util_service::Loopback around dispatch.
The api crate is now wire format only, and the transport that carries
its frames is the one every IPC service shares.

FdServer is the Dispatch impl. The newtype exists because Dispatch is a
foreign trait and cannot be implemented for every F: FdHandler directly.
dispatch() returns Result: a response buffer too small for even an error
frame is the one case with nothing to send back, where it used to return
a bare 0.

The request length check is now per opcode. expected_request_len(op)
returns the exact frame size for each operation, so a frame with
trailing slack is rejected before dispatch. The match has no catch-all,
so adding an opcode without choosing its length fails to compile.

Assisted-by: Claude
@chrysh
chrysh force-pushed the pldm-on-util-service branch from 6b407e6 to caeabc6 Compare September 26, 2026 17:38
@chrysh
chrysh marked this pull request as ready for review September 26, 2026 17:38
@chrysh
chrysh merged commit fb67d3c into OpenPRoT:ocp-global-demo-wip Sep 26, 2026
1 check passed
@chrysh
chrysh deleted the pldm-on-util-service branch September 26, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant